package com.ndk.head.usb;

import android.hardware.usb.UsbDevice;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.view.SurfaceHolder;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.herohan.uvcapp.CameraHelper;
import com.herohan.uvcapp.ICameraHelper;
import com.ndk.head.FaceTracker;
import com.ndk.head.R;
import com.ndk.head.Utils;
import com.serenegiant.usb.Size;
import com.serenegiant.usb.UVCCamera;
import com.serenegiant.usb.UVCParam;
import com.serenegiant.widget.AspectRatioSurfaceView;

import java.util.concurrent.ConcurrentLinkedQueue;


public class UsbCameraActivity extends AppCompatActivity {

    private static final boolean DEBUG = true;
    private static final String TAG = UsbCameraActivity.class.getSimpleName();

    private static final int DEFAULT_WIDTH = 640;
    private static final int DEFAULT_HEIGHT = 480;

    private TextView mCameraNameLeft;
    private UsbDevice mUsbDeviceLeft;
    private ICameraHelper mCameraHelperLeft;
    private AspectRatioSurfaceView svCameraViewLeft;

    private ConcurrentLinkedQueue<UsbDevice> mReadyUsbDeviceList = new ConcurrentLinkedQueue<>();
    private ConditionVariable mReadyDeviceConditionVariable = new ConditionVariable();

    private final Object mSync = new Object();

    private HandlerThread mHandlerThread;
    private Handler mAsyncHandler;

    private FaceTracker faceTracker;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_usb_camera);

        mCameraNameLeft = findViewById(R.id.textViewLeft);

        initViews();

        mHandlerThread = new HandlerThread(TAG);
        mHandlerThread.start();
        mAsyncHandler = new Handler(mHandlerThread.getLooper());

        initCameraHelper();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        mHandlerThread.quitSafely();
        mAsyncHandler.removeCallbacksAndMessages(null);
    }

    private void initViews() {
        setCameraViewLeft();
    }

    private void setCameraViewLeft() {
        svCameraViewLeft = findViewById(R.id.svCameraViewLeft);
        svCameraViewLeft.setAspectRatio(DEFAULT_WIDTH, DEFAULT_HEIGHT);

        svCameraViewLeft.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(@NonNull SurfaceHolder holder) {
                if (mCameraHelperLeft != null) {
                    mCameraHelperLeft.addSurface(holder.getSurface(), false);
                }
            }

            @Override
            public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
                if (faceTracker != null)
                    faceTracker.setSurface(holder.getSurface());

            }

            @Override
            public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
                if (mCameraHelperLeft != null) {
                    mCameraHelperLeft.removeSurface(holder.getSurface());
                }
                if (faceTracker != null)
                    faceTracker.setSurface(null);
            }
        });

        String path = Utils.copyAsset2Dir(this, "lbpcascade_frontalface.xml");
        faceTracker = new FaceTracker(path);
        faceTracker.start();
    }

    @Override
    protected void onStart() {
        if (DEBUG) Log.d(TAG, "huahua--onStart:");
        super.onStart();

    }

    @Override
    protected void onStop() {
        if (DEBUG) Log.d(TAG, "onStop:");
        super.onStop();
        clearCameraHelper();
        faceTracker.release();
    }

    public void initCameraHelper() {
        if (DEBUG) Log.d(TAG, "initCameraHelper:");
        if (mCameraHelperLeft == null) {
            mCameraHelperLeft = new CameraHelper();
            mCameraHelperLeft.setStateCallback(mStateListenerLeft);
        }
    }

    private void clearCameraHelper() {
        if (DEBUG) Log.d(TAG, "clearCameraHelper:");
        if (mCameraHelperLeft != null) {
            mCameraHelperLeft.release();
            mCameraHelperLeft = null;
        }
    }

    private void selectDeviceLeft(final UsbDevice device) {
        if (DEBUG) Log.v(TAG, "selectDeviceLeft:device=" + device.getDeviceName());
        mUsbDeviceLeft = device;

        mAsyncHandler.post(() -> {
            waitCanSelectDevice(device);

            if (mCameraHelperLeft != null) {
                mCameraHelperLeft.selectDevice(device);
            }
        });
    }

    /**
     * wait for only one camera need request permission
     *
     * @param device
     */
    private void waitCanSelectDevice(UsbDevice device) {
        mReadyUsbDeviceList.add(device);
        while (mReadyUsbDeviceList.size() > 1) {
            mReadyDeviceConditionVariable.block();
            mReadyDeviceConditionVariable.close();
        }
    }

    /**
     * remove ready camera that wait  for select
     *
     * @param device
     */
    private void removeSelectedDevice(UsbDevice device) {
        mReadyUsbDeviceList.remove(device);
        mReadyDeviceConditionVariable.open();
    }

    private final ICameraHelper.StateCallback mStateListenerLeft = new ICameraHelper.StateCallback() {
        private final String LOG_PREFIX = "ListenerLeft#";

        @Override
        public void onAttach(UsbDevice device) {
            if (DEBUG) Log.v(TAG, LOG_PREFIX + "onAttach:");
            synchronized (mSync) {
                if (mUsbDeviceLeft == null) {
                    selectDeviceLeft(device);
                    mCameraNameLeft.setText("左相机(" + device.getDeviceName() + ")");
                }
            }
        }

        @Override
        public void onDeviceOpen(UsbDevice device, boolean isFirstOpen) {
            if (DEBUG) Log.v(TAG, LOG_PREFIX + "onDeviceOpen:");
            if (mCameraHelperLeft != null && device.equals(mUsbDeviceLeft)) {
                UVCParam param = new UVCParam();
                param.setQuirks(UVCCamera.UVC_QUIRK_FIX_BANDWIDTH);
                mCameraHelperLeft.openCamera(param);
            }

            removeSelectedDevice(device);
        }

        @Override
        public void onCameraOpen(UsbDevice device) {
            if (DEBUG) Log.v(TAG, LOG_PREFIX + "onCameraOpen:");
            if (mCameraHelperLeft != null && device.equals(mUsbDeviceLeft)) {
                mCameraHelperLeft.startPreview();

                Size size = mCameraHelperLeft.getPreviewSize();
                if (size != null) {
                    int width = size.width;
                    int height = size.height;
                    //auto aspect ratio
                    svCameraViewLeft.setAspectRatio(width, height);
                }

                mCameraHelperLeft.addSurface(svCameraViewLeft.getHolder().getSurface(), false);

                mCameraHelperLeft.setFrameCallback(frame -> {


                    byte[] nv21 = new byte[frame.remaining()];
                    frame.get(nv21, 0, nv21.length);
                    Log.v(TAG, LOG_PREFIX + "nv21:"+nv21.length);
                    faceTracker.detect(nv21, 640, 480, 90);
                }, UVCCamera.PIXEL_FORMAT_NV21);
            }
        }

        @Override
        public void onCameraClose(UsbDevice device) {
            if (DEBUG) Log.v(TAG, LOG_PREFIX + "onCameraClose:");
            if (device.equals(mUsbDeviceLeft)) {
                if (mCameraHelperLeft != null) {
                    mCameraHelperLeft.removeSurface(svCameraViewLeft.getHolder().getSurface());
                }
            }
        }

        @Override
        public void onDeviceClose(UsbDevice device) {
            if (DEBUG) Log.v(TAG, LOG_PREFIX + "onDeviceClose:");
        }

        @Override
        public void onDetach(UsbDevice device) {
            if (DEBUG) Log.v(TAG, LOG_PREFIX + "onDetach:");
            if (device.equals(mUsbDeviceLeft)) {
                mUsbDeviceLeft = null;
            }

            removeSelectedDevice(device);
        }

        @Override
        public void onCancel(UsbDevice device) {
            if (DEBUG) Log.v(TAG, LOG_PREFIX + "onCancel:");
            if (device.equals(mUsbDeviceLeft)) {
                mUsbDeviceLeft = null;
            }

            removeSelectedDevice(device);
        }
    };

}